/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.gateway.server.manager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import com.iwindplus.boot.domain.vo.ResultVO;
import com.iwindplus.boot.util.JacksonUtil;
import com.iwindplus.gateway.server.domain.constant.GatewayConstant.RouteConstant;
import com.iwindplus.gateway.server.domain.converter.RouteDefinitionConverter;
import com.iwindplus.gateway.server.domain.property.GatewayProperty;
import com.iwindplus.gateway.server.service.RouteService;
import com.iwindplus.mgt.client.system.ServerClient;
import com.iwindplus.mgt.domain.dto.system.ServerDTO;
import com.iwindplus.mgt.domain.dto.system.ServerSaveEditBatchDTO;
import com.iwindplus.mgt.domain.vo.system.ServerRouteDefinitionVO;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

/**
 * 动态路由管理器（先从Nacos读取，读取不到从db读取，同步Nacos）.
 *
 * @author zengdegui
 * @since 2020/4/15
 */
@Slf4j
@Component
public class RouteManager implements ApplicationRunner {

    @Lazy
    @Resource
    private ServerClient serverClient;

    @Resource
    private RouteService routeService;

    @Resource
    private NacosConfigManager nacosConfigManager;

    @Resource
    private GatewayProperty property;

    /**
     * 1.先从Nacos读取，同步DB，2.读取不到从db读取，同步Nacos.
     *
     * @param dataId 文件名
     * @param group  分组
     */
    public void loadRoute(String dataId, String group) {
        if (Objects.nonNull(this.nacosConfigManager)) {
            this.refreshRouteByNacos(dataId, group, null);
        } else {
            this.refreshRouteByDb();
        }
    }

    /**
     * 从Nacos读取，刷新路由，同步配置至DB.
     *
     * @param dataId     文件名
     * @param group      分组
     * @param configInfo 配置信息
     * @return boolean
     */
    private void refreshRouteByNacos(String dataId, String group, String configInfo) {
        // 从Nacos读取
        List<ServerRouteDefinitionVO> routeDefinitions = this.listRouteDefinitionByNacos(dataId, group, configInfo);
        if (CollUtil.isNotEmpty(routeDefinitions)) {
            this.refreshRoute(routeDefinitions);
            if (Boolean.TRUE.equals(this.property.getRoute().getWriteNacos())) {
                // 同步DB
                this.writeDataToDb(routeDefinitions);
            }
        }
    }

    /**
     * 从db读取，刷新路由，同步配置至Nacos.
     */
    public void refreshRouteByDb() {
        // 从DB读取
        List<ServerRouteDefinitionVO> routeDefinitions = this.listRouteDefinitionByDb();
        if (CollUtil.isNotEmpty(routeDefinitions)) {
            this.refreshRoute(routeDefinitions);
            // 同步Nacos
            if (Boolean.TRUE.equals(this.property.getRoute().getWriteNacos())) {
                this.writeDataToNacos(routeDefinitions);
            }
        }
    }

    private void refreshRoute(List<ServerRouteDefinitionVO> routeDefinitions) {
        List<RouteDefinition> entities = new ArrayList<>(10);
        routeDefinitions.stream().forEach(routeDefinition -> entities.add(RouteDefinitionConverter.convertToRouteDefinition(routeDefinition)));
        if (CollUtil.isNotEmpty(entities)) {
            this.routeService.refreshRoute(entities);
        }
    }

    private void writeDataToNacos(List<ServerRouteDefinitionVO> routeDefinitions) {
        try {
            ConfigService configService = this.nacosConfigManager.getConfigService();
            String content = JacksonUtil.toJsonPrettyStr(routeDefinitions);
            configService.publishConfig(RouteConstant.NACOS_GATEWAY_ROUTER, RouteConstant.NACOS_GATEWAY_GROUP, content, ConfigType.JSON.getType());
            log.info("将服务路由写入Nacos成功");
        } catch (NacosException ex) {
            log.error("Nacos write data exception, message={}", ex.getMessage(), ex);
        }
    }

    private void writeDataToDb(List<ServerRouteDefinitionVO> routeDefinitions) {
        try {
            List<ServerDTO> serverList = new ArrayList<>(10);
            routeDefinitions.stream().forEach(routeDefinition -> serverList.add(RouteDefinitionConverter.convertToServer(routeDefinition)));
            if (CollUtil.isEmpty(serverList)) {
                return;
            }
            ServerSaveEditBatchDTO entities = ServerSaveEditBatchDTO.builder()
                .entities(serverList)
                .build();
            this.serverClient.saveOrEditBatch(entities);
            log.info("将服务路由写入DB成功");
        } catch (Exception ex) {
            log.error("DB write data exception, message={}", ex.getMessage(), ex);
        }
    }

    private List<ServerRouteDefinitionVO> listRouteDefinitionByNacos(String dataId, String group, String configInfo) {
        if (Objects.nonNull(this.nacosConfigManager) && CharSequenceUtil.isBlank(configInfo)) {
            try {
                ConfigService configService = this.nacosConfigManager.getConfigService();
                configInfo = configService.getConfig(dataId, group, 5000);
            } catch (NacosException ex) {
                log.error("Nacos load route exception, message={}", ex.getMessage(), ex);
            }
        }
        return this.parseRouteDefinitions(configInfo);
    }

    private List<ServerRouteDefinitionVO> parseRouteDefinitions(String configInfo) {
        List<ServerRouteDefinitionVO> routeDefinitions = new ArrayList<>(10);
        if (CharSequenceUtil.isNotBlank(configInfo)) {
            routeDefinitions = JacksonUtil.parseObject(configInfo, new TypeReference<>() {
            });
            log.info("从Nacos加载服务路由成功");
        }
        // 去重
        if (CollUtil.isNotEmpty(routeDefinitions)) {
            routeDefinitions = this.removeDuplicates(routeDefinitions);
        }
        return routeDefinitions;
    }

    private List<ServerRouteDefinitionVO> listRouteDefinitionByDb() {
        final ResultVO<List<ServerRouteDefinitionVO>> list = this.serverClient.listRouteDefinition();
        list.errorThrow();
        log.info("从DB加载服务路由成功");
        return list.getBizData();
    }

    /**
     * Nacos监听.
     *
     * @param dataId 文件名
     * @param group  分组
     */
    public void nacosListener(String dataId, String group) {
        ConfigService configService = this.nacosConfigManager.getConfigService();
        try {
            configService.addListener(dataId, group, new NacosListener(dataId, group));
        } catch (NacosException ex) {
            log.error("Nacos listener exception, message={}", ex.getMessage(), ex);
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        String dataId = RouteConstant.NACOS_GATEWAY_ROUTER;
        String group = RouteConstant.NACOS_GATEWAY_GROUP;
        if (Objects.nonNull(this.nacosConfigManager)) {
            // 创建Nacos监听
            this.nacosListener(dataId, group);
        }
        try {
            // 加载路由
            this.loadRoute(dataId, group);
        } catch (Exception ex) {
            log.error(ExceptionConstant.EXCEPTION, ex);
        }
    }

    private List<ServerRouteDefinitionVO> removeDuplicates(List<ServerRouteDefinitionVO> routeDefinitions) {
        return routeDefinitions.stream()
            .collect(Collectors.toMap(ServerRouteDefinitionVO::getId, Function.identity(), (existing, duplicate) -> existing))
            .values()
            .stream()
            .collect(Collectors.toCollection(ArrayList::new));
    }

    @Data
    @AllArgsConstructor
    private class NacosListener implements Listener {

        /**
         * 文件名
         */
        private String dataId;

        /**
         * 分组
         */
        private String group;

        @Override
        public Executor getExecutor() {
            return null;
        }

        @Override
        public void receiveConfigInfo(String configInfo) {
            refreshRouteByNacos(dataId, group, configInfo);
        }
    }
}
